wayland: prefer subsurface when possible
authorOlivier Fourdan <ofourdan@redhat.com>
Mon, 21 Dec 2015 13:35:53 +0000 (14:35 +0100)
committerOlivier Fourdan <ofourdan@redhat.com>
Fri, 8 Jan 2016 09:33:26 +0000 (10:33 +0100)
Quite a few applications use GTK_WINDOW_POPUP to create various
temporary windows and place then on screen. That works fine on X11 but
on Wayland there is no global coordinate system for regular surfaces.

If the application is using a gdk temp window and set a parent with
gtk_window_transient_for(), the gdk wayland backend has all it needs to
create a subsurface that can be placed at will by the application.

Bugzilla: https://bugzilla.gnome.org/show_bug.cgi?id=759738

gdk/wayland/gdkwindow-wayland.c
gtk/gtkwindow.c
tests/Makefile.am
tests/testpopup.c [new file with mode: 0644]

index 7dff2569147cc27bd2c3c6cc80aacaa50f7c932d..1e8863311f0422503ef30f98130c36cef9137770 100644 (file)
@@ -1228,20 +1228,35 @@ should_be_mapped (GdkWindow *window)
 }
 
 static gboolean
-should_map_as_subsurface (GdkWindow *window)
+should_map_as_popup (GdkWindow *window)
 {
   GdkWindowImplWayland *impl = GDK_WINDOW_IMPL_WAYLAND (window->impl);
 
-  if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_SUBSURFACE)
-    return TRUE;
+  /* Ideally, popup would be temp windows with a parent and grab */
+  if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_TEMP)
+    {
+      /* If a temp window has a parent and a grab, we can use a popup */
+      if (impl->transient_for)
+        {
+          if (impl->grab_input_seat)
+            return TRUE;
+        }
+      else
+        g_warning ("Window %p is a temporary window without parent, "
+                   "application will not be able to position it on screen.",
+                   window);
+    }
 
+  /* Yet we need to keep the window type hint tests for compatibility */
   switch (impl->hint)
     {
-    case GDK_WINDOW_TYPE_HINT_TOOLTIP:
+    case GDK_WINDOW_TYPE_HINT_POPUP_MENU:
+    case GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU:
+    case GDK_WINDOW_TYPE_HINT_COMBO:
       return TRUE;
 
     case GDK_WINDOW_TYPE_HINT_UTILITY:
-      if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_TEMP)
+      if (GDK_WINDOW_TYPE (window) != GDK_WINDOW_TEMP)
         return TRUE;
       break;
 
@@ -1253,24 +1268,32 @@ should_map_as_subsurface (GdkWindow *window)
 }
 
 static gboolean
-should_map_as_popup (GdkWindow *window)
+should_map_as_subsurface (GdkWindow *window)
 {
   GdkWindowImplWayland *impl = GDK_WINDOW_IMPL_WAYLAND (window->impl);
 
-  switch (impl->hint)
+  if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_SUBSURFACE)
+    return TRUE;
+
+  if (GDK_WINDOW_TYPE (window) != GDK_WINDOW_TEMP)
+    return FALSE;
+
+  /* if we want a popup, we do not want a subsurface */
+  if (should_map_as_popup (window))
+    return FALSE;
+
+  if (impl->transient_for)
     {
-    case GDK_WINDOW_TYPE_HINT_POPUP_MENU:
-    case GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU:
-    case GDK_WINDOW_TYPE_HINT_COMBO:
-      return TRUE;
+      GdkWindowImplWayland *impl_parent;
 
-    case GDK_WINDOW_TYPE_HINT_UTILITY:
-      if (GDK_WINDOW_TYPE (window) != GDK_WINDOW_TEMP)
+      impl_parent = GDK_WINDOW_IMPL_WAYLAND (impl->transient_for->impl);
+      /* subsurface require that the parent is mapped */
+      if (impl_parent->mapped)
         return TRUE;
-      break;
+      else
+        g_warning ("Couldn't map window %p as susburface because its parent is not mapped.",
+                   window);
 
-    default:
-      break;
     }
 
   return FALSE;
@@ -1313,7 +1336,7 @@ gdk_wayland_window_map (GdkWindow *window)
       if (impl->transient_for)
         gdk_wayland_window_create_subsurface (window);
       else
-        g_warning ("Couldn't map as window %p as susburface yet because it doesn't have a parent",
+        g_warning ("Couldn't map window %p as susburface yet because it doesn't have a parent",
                    window);
     }
   else if (should_map_as_popup (window))
index b929a9c618fbf70d317cee21c18363cba93ca3b7..8b4e86a87f4b4bc0ae303e60949cd768f29ff2ce 100644 (file)
@@ -3229,6 +3229,12 @@ gtk_window_unset_transient_for  (GtkWindow *window)
  *
  * Passing %NULL for @parent unsets the current transient window.
  *
+ * On Wayland, this function can also be used to attach a new
+ * #GTK_WINDOW_POPUP to a #GTK_WINDOW_TOPLEVEL parent already mapped
+ * on screen so that the #GTK_WINDOW_POPUP will be created as a
+ * subsurface-based window #GDK_WINDOW_SUBSURFACE which can be
+ * positioned at will relatively to the #GTK_WINDOW_TOPLEVEL surface.
+ *
  * On Windows, this function puts the child window on top of the parent,
  * much as the window manager would have done on X.
  */
index f2123d97dba877ab2c221cef48701e083ad41b70..85d06bae6cdf20e4da8c8a09091298d7019be8e1 100644 (file)
@@ -170,6 +170,7 @@ noinst_PROGRAMS =  $(TEST_PROGS)    \
        gdkgears                        \
        listmodel                       \
        foreigndrawing                  \
+       testpopup                       \
        $(NULL)
 
 if USE_X11
diff --git a/tests/testpopup.c b/tests/testpopup.c
new file mode 100644 (file)
index 0000000..cd82468
--- /dev/null
@@ -0,0 +1,68 @@
+#include <gtk/gtk.h>
+
+static gboolean
+draw_popup (GtkWidget *widget,
+            cairo_t   *cr,
+            gpointer   data)
+{
+  cairo_set_source_rgb (cr, 1, 0, 0);
+  cairo_paint (cr);
+
+  return FALSE;
+}
+
+static gboolean
+place_popup (GtkWidget *parent,
+             GdkEvent  *event,
+             GtkWidget *popup)
+{
+  GdkEventMotion *ev_motion = (GdkEventMotion *) event;
+  gint width, height;
+
+  gtk_window_get_size (GTK_WINDOW (popup), &width, &height);
+  gtk_window_move (GTK_WINDOW (popup),
+                   (int) ev_motion->x_root - width / 2,
+                   (int) ev_motion->y_root - height / 2);
+
+  return FALSE;
+}
+
+static gboolean
+on_map_event (GtkWidget *parent,
+              GdkEvent  *event,
+              gpointer   data)
+{
+  GtkWidget *popup;
+
+  popup = gtk_window_new (GTK_WINDOW_POPUP);
+
+  gtk_widget_set_size_request (GTK_WIDGET (popup), 20, 20);
+  gtk_widget_set_app_paintable (GTK_WIDGET (popup), TRUE);
+  gtk_window_set_transient_for (GTK_WINDOW (popup), GTK_WINDOW (parent));
+  g_signal_connect (popup, "draw", G_CALLBACK (draw_popup), NULL);
+  g_signal_connect (parent, "motion-notify-event", G_CALLBACK (place_popup), popup);
+
+  gtk_widget_show (popup);
+
+  return FALSE;
+}
+
+int
+main (int argc, char *argv[])
+{
+  GtkWidget *window;
+
+  gtk_init (&argc, &argv);
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+  gtk_widget_set_events (window, GDK_POINTER_MOTION_MASK);
+  g_signal_connect (window, "destroy", gtk_main_quit, NULL);
+  g_signal_connect (window, "map-event", G_CALLBACK (on_map_event), NULL);
+
+  gtk_widget_show (window);
+
+  gtk_main ();
+
+  return 0;
+}